package com.hunttown.common.dao.base;

import com.ibatis.sqlmap.client.SqlMapExecutor;
import com.ibatis.sqlmap.client.event.RowHandler;
import com.hunttown.common.dao.datasource.MasterSlaveSwitcher;
import com.hunttown.common.dao.sharding.ShardingRouters;
import com.hunttown.common.dao.sharding.ShardingUtil;
import com.hunttown.common.sharding.client.datasource.ShardingDataSources;
import com.hunttown.common.sharding.client.support.RouteResult;

import java.sql.SQLException;
import java.util.List;
import java.util.Map;
import javax.sql.DataSource;

import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.dao.DataAccessException;
import org.springframework.orm.ibatis.SqlMapClientCallback;
import org.springframework.util.Assert;

public abstract class BaseShardingDao extends AbstractDao implements ApplicationContextAware {
    private static ShardingDataSources shardingDataSources = ShardingDataSources.getInstance();
    private static ShardingRouters shardingRouters;

    public BaseShardingDao() {
    }

    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        super.setApplicationContext(applicationContext);
        if (shardingRouters == null && applicationContext.containsBeanDefinition("shardingRouters")) {
            shardingRouters = (ShardingRouters) applicationContext.getBean("shardingRouters", ShardingRouters.class);
        }

        Assert.notNull(this.getSqlMapClient(), "sqlMapClient cannot be null.");
    }

    protected Object insert(final String statementName, final Object parameterObject) throws DataAccessException {
        return this.executeWithObjectResult(this.getSingleDataSource(parameterObject), new SqlMapClientCallback<Object>() {
            public Object doInSqlMapClient(SqlMapExecutor executor) throws SQLException {
                return executor.insert(statementName, parameterObject);
            }
        });
    }

    protected int update(final String statementName, final Object parameterObject) throws DataAccessException {
        return ((Integer) this.executeWithObjectResult(this.getSingleDataSource(parameterObject), new SqlMapClientCallback<Integer>() {
            public Integer doInSqlMapClient(SqlMapExecutor executor) throws SQLException {
                return executor.update(statementName, parameterObject);
            }
        })).intValue();
    }

    protected int delete(final String statementName, final Object parameterObject) throws DataAccessException {
        return ((Integer) this.executeWithObjectResult(this.getSingleDataSource(parameterObject), new SqlMapClientCallback<Integer>() {
            public Integer doInSqlMapClient(SqlMapExecutor executor) throws SQLException {
                return executor.delete(statementName, parameterObject);
            }
        })).intValue();
    }

    protected <T> T queryForObject(final String statementName, final Object parameterObject) throws DataAccessException {
        if (this.isNonMasterSlaveMode()) {
            return this.executeWithObjectResult(this.getSingleDataSource(parameterObject), new SqlMapClientCallback<T>() {
                public T doInSqlMapClient(SqlMapExecutor executor) throws SQLException {
                    return (T) executor.queryForObject(statementName, parameterObject);
                }
            });
        } else {
            boolean isNotForceReadMaster = !MasterSlaveSwitcher.isForceReadMaster();
            if (isNotForceReadMaster) {
                MasterSlaveSwitcher.readSlave();
            }

            Object var4;
            try {
                var4 = this.executeWithObjectResult(this.getSingleDataSource(parameterObject), new SqlMapClientCallback<T>() {
                    public T doInSqlMapClient(SqlMapExecutor executor) throws SQLException {
                        return (T) executor.queryForObject(statementName, parameterObject);
                    }
                });
            } finally {
                if (isNotForceReadMaster) {
                    MasterSlaveSwitcher.clearMasterSlave();
                }

            }

            return (T) var4;
        }
    }

    protected <T> T queryForObject(final String statementName, final Object parameterObject, final Object resultObject) throws DataAccessException {
        if (this.isNonMasterSlaveMode()) {
            return this.executeWithObjectResult(this.getSingleDataSource(parameterObject), new SqlMapClientCallback<T>() {
                public T doInSqlMapClient(SqlMapExecutor executor) throws SQLException {
                    return (T) executor.queryForObject(statementName, parameterObject, resultObject);
                }
            });
        } else {
            boolean isNotForceReadMaster = !MasterSlaveSwitcher.isForceReadMaster();
            if (isNotForceReadMaster) {
                MasterSlaveSwitcher.readSlave();
            }

            Object var5;
            try {
                var5 = this.executeWithObjectResult(this.getSingleDataSource(parameterObject), new SqlMapClientCallback<T>() {
                    public T doInSqlMapClient(SqlMapExecutor executor) throws SQLException {
                        return (T) executor.queryForObject(statementName, parameterObject, resultObject);
                    }
                });
            } finally {
                if (isNotForceReadMaster) {
                    MasterSlaveSwitcher.clearMasterSlave();
                }

            }

            return (T) var5;
        }
    }

    protected <K, V> Map<K, V> queryForMap(final String statementName, final Object parameterObject, final String keyProperty) throws DataAccessException {
        if (this.isNonMasterSlaveMode()) {
            return (Map) this.executeWithObjectResult(this.getSingleDataSource(parameterObject), new SqlMapClientCallback<Map<K, V>>() {
                public Map<K, V> doInSqlMapClient(SqlMapExecutor executor) throws SQLException {
                    return executor.queryForMap(statementName, parameterObject, keyProperty);
                }
            });
        } else {
            boolean isNotForceReadMaster = !MasterSlaveSwitcher.isForceReadMaster();
            if (isNotForceReadMaster) {
                MasterSlaveSwitcher.readSlave();
            }

            Map var5;
            try {
                var5 = (Map) this.executeWithObjectResult(this.getSingleDataSource(parameterObject), new SqlMapClientCallback<Map<K, V>>() {
                    public Map<K, V> doInSqlMapClient(SqlMapExecutor executor) throws SQLException {
                        return executor.queryForMap(statementName, parameterObject, keyProperty);
                    }
                });
            } finally {
                if (isNotForceReadMaster) {
                    MasterSlaveSwitcher.clearMasterSlave();
                }

            }

            return var5;
        }
    }

    protected <K, V> Map<K, V> queryForMap(final String statementName, final Object parameterObject, final String keyProperty, final String valueProperty) throws DataAccessException {
        if (this.isNonMasterSlaveMode()) {
            return (Map) this.executeWithObjectResult(this.getSingleDataSource(parameterObject), new SqlMapClientCallback<Map<K, V>>() {
                public Map<K, V> doInSqlMapClient(SqlMapExecutor executor) throws SQLException {
                    return executor.queryForMap(statementName, parameterObject, keyProperty, valueProperty);
                }
            });
        } else {
            boolean isNotForceReadMaster = !MasterSlaveSwitcher.isForceReadMaster();
            if (isNotForceReadMaster) {
                MasterSlaveSwitcher.readSlave();
            }

            Map var6;
            try {
                var6 = (Map) this.executeWithObjectResult(this.getSingleDataSource(parameterObject), new SqlMapClientCallback<Map<K, V>>() {
                    public Map<K, V> doInSqlMapClient(SqlMapExecutor executor) throws SQLException {
                        return executor.queryForMap(statementName, parameterObject, keyProperty, valueProperty);
                    }
                });
            } finally {
                if (isNotForceReadMaster) {
                    MasterSlaveSwitcher.clearMasterSlave();
                }

            }

            return var6;
        }
    }

    protected <T> List<T> queryForList(final String statementName, final Object parameterObject) throws DataAccessException {
        if (this.isNonMasterSlaveMode()) {
            return (List) this.executeWithObjectResult(this.getSingleDataSource(parameterObject), new SqlMapClientCallback<List<T>>() {
                public List<T> doInSqlMapClient(SqlMapExecutor executor) throws SQLException {
                    return executor.queryForList(statementName, parameterObject);
                }
            });
        } else {
            boolean isNotForceReadMaster = !MasterSlaveSwitcher.isForceReadMaster();
            if (isNotForceReadMaster) {
                MasterSlaveSwitcher.readSlave();
            }

            List var4;
            try {
                var4 = (List) this.executeWithObjectResult(this.getSingleDataSource(parameterObject), new SqlMapClientCallback<List<T>>() {
                    public List<T> doInSqlMapClient(SqlMapExecutor executor) throws SQLException {
                        return executor.queryForList(statementName, parameterObject);
                    }
                });
            } finally {
                if (isNotForceReadMaster) {
                    MasterSlaveSwitcher.clearMasterSlave();
                }

            }

            return var4;
        }
    }

    protected void queryWithRowHandler(final String statementName, final Object parameterObject, final RowHandler rowHandler) throws DataAccessException {
        if (this.isNonMasterSlaveMode()) {
            this.executeWithObjectResult(this.getSingleDataSource(parameterObject), new SqlMapClientCallback<Object>() {
                public Object doInSqlMapClient(SqlMapExecutor executor) throws SQLException {
                    executor.queryWithRowHandler(statementName, parameterObject, rowHandler);
                    return null;
                }
            });
        }

        boolean isNotForceReadMaster = !MasterSlaveSwitcher.isForceReadMaster();
        if (isNotForceReadMaster) {
            MasterSlaveSwitcher.readSlave();
        }

        try {
            this.executeWithObjectResult(this.getSingleDataSource(parameterObject), new SqlMapClientCallback<Object>() {
                public Object doInSqlMapClient(SqlMapExecutor executor) throws SQLException {
                    executor.queryWithRowHandler(statementName, parameterObject, rowHandler);
                    return null;
                }
            });
        } finally {
            if (isNotForceReadMaster) {
                MasterSlaveSwitcher.clearMasterSlave();
            }

        }

    }

    protected <T> T executeUpdate(final SqlMapClientCallback<T> action, Object sharding) throws DataAccessException {
        return this.executeWithObjectResult(this.getSingleDataSource(sharding), new SqlMapClientCallback<T>() {
            public T doInSqlMapClient(SqlMapExecutor executor) throws SQLException {
                return action.doInSqlMapClient(executor);
            }
        });
    }

    protected <T> T executeFind(final SqlMapClientCallback<T> action, Object sharding) throws DataAccessException {
        return this.executeWithObjectResult(this.getSingleDataSource(sharding), new SqlMapClientCallback<T>() {
            public T doInSqlMapClient(SqlMapExecutor executor) throws SQLException {
                return action.doInSqlMapClient(executor);
            }
        });
    }

    protected <T> T executeWithObjectResult(DataSource dataSource, SqlMapClientCallback<T> callback) throws DataAccessException {
        return this.executeWithObjectResult(this.getSqlMapClient(), dataSource, callback);
    }

    protected DataSource getSingleDataSource(Object sharding) {
        RouteResult routeResult = ShardingUtil.doRoute(shardingRouters, sharding);
        Assert.notEmpty(routeResult.getResourceIdentities(), "cannot find a datasource for physicalTableName : " + routeResult.getPhysicalTableName());
        return shardingDataSources.getDataSource((String) routeResult.getResourceIdentities().get(0));
    }
}